﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using SuiHan.Commons.Daemon.Util;
using SuiHan.Commons.Logging;

namespace SuiHan.Commons.Daemon {
    /// <summary>
    /// 监听程序路径守护进程
    /// </summary>
    public class ProcessPathDaemon : DaemonProcess {
        private static readonly Logger Log = LoggerFactory.GetCurrentLogger();

        /// <summary>
        /// 可执行程序路径
        /// </summary>
        public string ExecutorPath { get; }
        public string RunScript { get; }

        /// <summary>
        /// 进程名称
        /// </summary>
        public string ProcessName { get; }

        /// <summary>
        /// 程序运行参数
        /// </summary>
        public string RunArguments { get; }

        private static readonly object processesLock = new();
        private static Process[] currentProcesses;
        private readonly Logger logger;
        private Process process;

        public ProcessPathDaemon(string executorPath, string runScript = null, string arguments = "") {
            ExecutorPath = executorPath;
            RunScript = runScript;
            ProcessName = GetProcessName(executorPath);
            RunArguments = arguments;
            logger = LoggerFactory.GetLogger($"{typeof(ProcessPathDaemon).FullName}[{ProcessName}]");
        }

        private static void LoadProcesses() {
            lock (processesLock) {
                if (currentProcesses != null) {
                    foreach (Process p in currentProcesses) {
                        try {
                            p.Dispose();
                        } catch (Exception e) {
                            Log.Error(e);
                        }
                    }

                    currentProcesses = null;
                }

                currentProcesses ??= Process.GetProcesses();
            }
        }

        protected override void BeforeRun() {
            LoadProcesses();
            lock (processesLock) {
                try {
                    process = currentProcesses.FirstOrDefault(it => {
                        try {
                            return it.MainModule?.FileName == ExecutorPath;
                        } catch (Exception) {
                            // 有些进程无权访问
                            return false;
                        }
                    });
                } catch (Exception e) {
                    logger.Error(e);
                }
            }
        }

        protected override bool IsRunning() {
            return process != null;
        }

        protected override void RunProcess() {
            try {
                logger.Debug("程序正在启动中");
                ProcessStartInfo startInfo = new(RunScript ?? ExecutorPath, RunArguments) {
                    CreateNoWindow = true,
                    UseShellExecute = false,
                    WindowStyle = ProcessWindowStyle.Hidden,
                    RedirectStandardOutput = true,
                    WorkingDirectory = Path.GetDirectoryName(ExecutorPath) ?? ""
                };

                using Process process = Process.Start(startInfo);
                if (process != null) {
                    process.OutputDataReceived += (_, args) => { Console.WriteLine(args.Data); };
                    process.ErrorDataReceived += (_, args) => { Console.WriteLine(args.Data); };
                    logger.Debug("程序启动成功");
                } else {
                    logger.Error("程序启动失败");
                }
            } catch (Exception e) {
                logger.Error($"程序启动失败: {e.Message} {ExecutorPath}");
            }
        }

        public override void KillProcess() {
            if (process == null) {
                BeforeRun();
                if (process == null) return;
            }

            try {
                process?.Kill();
                process?.Dispose();
                logger.Debug($"杀死进程: {ExecutorPath}");
            } catch (Exception e) {
                logger.Error($"杀死进程异常, PID: {process.Id} Path: {ExecutorPath} Error: {e.Message}");
            } finally {
                process = null;
            }
        }

        /// <summary>
        /// 返回当前计算中所有监听的端口
        /// </summary>
        private static List<NetworkEndPoint> GetCurrentListeningPort() {
            List<NetworkEndPoint> ports = new();

            Process process = new();
            process.StartInfo = new ProcessStartInfo("netstat", "-ano") {
                CreateNoWindow = true,
                UseShellExecute = false,
                WindowStyle = ProcessWindowStyle.Hidden,
                RedirectStandardOutput = true
            };
            process.Start();
            string output = process.StandardOutput.ReadToEnd();
            using StringReader reader = new(output);
            string line;
            while ((line = reader.ReadLine()?.Trim()) != null) {
                if (!line.StartsWith("TCP") && !line.StartsWith("UDP")) continue;

                line = new Regex("\\s+").Replace(line, " ");
                string[] strings = line.Split(new[] { ' ' }, 5);
                if (strings[3] != "LISTENING") continue;

                int colonIndex = strings[1].LastIndexOf(':');
                int listeningPort = int.Parse(strings[1].Substring(colonIndex + 1));

                int pid = int.Parse(strings[4]);
                Process proc = GetProcess(pid);
                NetworkEndPoint endPoint = new() {
                    Port = listeningPort,
                    PID = pid,
                    ProcessName = proc?.ProcessName,
                    FilePath = proc?.MainModule?.FileName,
                    ProcessObj = proc,
                };
                ports.Add(endPoint);
            }

            return ports.Distinct().ToList();
        }

        private static Process GetProcess(int pid) {
            try {
                return Process.GetProcessById(pid);
            } catch (Exception) {
                // ignored
            }

            return null;
        }

        private static string GetProcessName(string path) {
            string fileName = Path.GetFileName(path);
            int dotIndex = fileName.LastIndexOf('.');
            return fileName.Substring(0, dotIndex);
        }
    }
}
